﻿//*************************************************************************
// EnecsyProxy Gateway Copyright (C) 215 by Roger Zander (roger@zander.ch)
//*************************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.IO.Ports;
using System.Net.NetworkInformation;
using uPLibrary.Networking.M2Mqtt;

namespace EnecsysProxy
{
    class Program
    {
        static public List<EnecsysData> InverterData = new List<EnecsysData>();
        static public MqttClient oMQTT;
        static public System.Timers.Timer tPVOutput = new System.Timers.Timer(Properties.Settings.Default.PVOutputInterval); //300000= 5min
		static public System.Timers.Timer tWatchdog = new System.Timers.Timer(4000);

		static int iCount = 0;
        static DateTime dStart = DateTime.Now;


        static void Main(string[] args)
		{
            Console.WriteLine("Enecsys Server (C) 2015 by Roger Zander");

            try
            {
                //Connect an MQTT Server if specified (add M2Mqtt as Reference)
                if (!string.IsNullOrEmpty(Properties.Settings.Default.MQTTBroker))
                {
                    oMQTT = new MqttClient(Properties.Settings.Default.MQTTBroker);
                    oMQTT.Connect("ExPvProxy");

                    if (oMQTT.IsConnected)
                    {
                        oMQTT.Publish("Enecsys/Service/StartTime", Encoding.UTF8.GetBytes(DateTime.Now.ToString()), 0, false);
                    }
                }
                else
                {
                    oMQTT = new MqttClient("127.0.0.1");
                }
            }
            catch { }


            tPVOutput.Elapsed += tPVOutput_Elapsed;
			tWatchdog.Elapsed += tWatchdog_Elapsed;
			tPVOutput.Enabled = Properties.Settings.Default.PVOutputEnabled;
			tWatchdog.Enabled = Properties.Settings.Default.RPWatchdog;

            Thread TCPThread = new Thread(new ThreadStart(createListener));
            TCPThread.Start();

            Console.WriteLine("Enecsys Server is listening on Port 5040 ...");
		}

        public static void createListener()
        {
            TcpListener tcpListener = null;
            try
            {
                tcpListener = new TcpListener(IPAddress.Any, 5040);
                tcpListener.Start();
            }
            catch (Exception e)
            {
                e.ToString();
            }
            while (true)
            {
                try
                {
                    Thread.Sleep(10);
                    TcpClient tcpClient = tcpListener.AcceptTcpClient();

                    NetworkStream stream = tcpClient.GetStream();
                    string mstrResponse = "Enecsys Server connected\n\r";  //?

                    byte[] bytesSent = Encoding.ASCII.GetBytes(mstrResponse);
                    stream.Write(bytesSent, 0, bytesSent.Length);

                    while (tcpClient.Connected)
                    {
                        try
                        {
                            byte[] bytes = new byte[1024];
                            stream.Read(bytes, 0, bytes.Length);
                            SocketHelper helper = new SocketHelper();
                            helper.processMsg(tcpClient, stream, bytes);
                        }
                        catch
                        {
                            Console.WriteLine("E184: Disconnect");
                            tcpClient.Close();
                        }
                    }

                }
                catch (Exception ex)
                {
                    ex.ToString();
                }
            }
        }

        static void tWatchdog_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			try {
				Ping pingSender = new Ping ();
				PingOptions options = new PingOptions ();
				options.DontFragment = true;
				bool bSuccess = true;
				if (iCount >= 9) {
					//Ping CCU to check if LAN is alive
					PingReply reply = pingSender.Send (Properties.Settings.Default.CCU_IP);
					if (reply.Status == IPStatus.Success) {
						Console.WriteLine ("CCU is alive...");
						iCount = 0;
					} else {
						bSuccess = false;
					}
				}

				if (bSuccess) {
					System.IO.FileStream fs = System.IO.File.OpenWrite ("/dev/watchdog");
					using (System.IO.StreamWriter sw = new System.IO.StreamWriter (fs)) {
						sw.WriteLine ("Blah");
					}
				}
				iCount++;
			} catch (Exception ex) {
				Console.WriteLine (ex.Message);
			}
		}

        static void tPVOutput_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {

            string sTempAussen = "20";

            if (Properties.Settings.Default.PVOutputEnabled)
            {
                try
                {
                    PVOutput oPV = new PVOutput(Properties.Settings.Default.PVOutputKEY, Properties.Settings.Default.PVOutputSID);
                    DateTime dAge = DateTime.Now - new TimeSpan(0, 15, 0);
                    double dPVPower = 0;
                    double dPowerUsage = 0;

                    if (InverterData.Count > 0)
                    {
                        dPVPower = InverterData.Where(t => t.LastModified >= dAge).Sum(t => t.DCPowerW);
                    }

                    oPV.SendSolarOutput(dPVPower, sTempAussen, InverterData.Where(t => t.LastModified >= dAge).Sum(t => t.VoltageAC), -1);
                    Console.WriteLine("PVOut Power Total: " + dPowerUsage.ToString("N0") + "W");


                    //Clear existing Data...
                    InverterData = InverterData.Where(t => t.LastModified >= dAge).ToList();

                }
                catch (Exception ex)
                {
                    Console.WriteLine("E438:" + ex.Message);
                }
            }
        }
    }

    class SocketHelper
    {
        TcpClient mscClient;
        string mstrMessage;
        string mstrResponse;
        byte[] bytesSent;
        public void processMsg(TcpClient client, NetworkStream stream, byte[] bytesReceived)
        {
            try
            {
                // Handle the message received and  
                // send a response back to the client.
                mstrMessage = Encoding.ASCII.GetString(bytesReceived, 0, bytesReceived.Length);
                mscClient = client;

                string[] aMsgs = mstrMessage.Split('\r');
                aMsgs.ToString();
                foreach (string sLine in aMsgs)
                {
                    if (sLine.Substring(18, 3) == "WS=")
                    {
                        string sSub = (sLine.Substring(18));
                        try
                        {
                            EnecsysData EDs = new EnecsysData(sSub);
                            EnecsysData EDItem = Program.InverterData.SingleOrDefault(t => t.SystemID == EDs.SystemID);
                            if (EDItem == null)
                            {
                                EDItem = EDs;
                                Program.InverterData.Add(EDItem);
                            }
                            else
                            {
                                Program.InverterData[Program.InverterData.IndexOf(EDItem)] = EDs;
                            }
                            Console.WriteLine("Time:" + DateTime.Now.ToShortTimeString() + ", Panel:" + EDs.SystemID + ", Power:" + EDs.DCPowerW + "W");

                            try
                            {
                                if (Program.oMQTT.IsConnected)
                                {
                                    Program.oMQTT.Publish("Enecsys/Data/" + EDs.SystemID + "/DCPowerW", Encoding.UTF8.GetBytes(EDs.DCPowerW.ToString("N0").Replace("'", "")), 0, false);
                                    Program.oMQTT.Publish("Enecsys/Data/" + EDs.SystemID + "/LifekWh", Encoding.UTF8.GetBytes(EDs.LifekWh.ToString("N0").Replace("'", "")), 0, false);
                                    Program.oMQTT.Publish("Enecsys/Data/" + EDs.SystemID + "/Efficiency", Encoding.UTF8.GetBytes(EDs.Efficiency.ToString("N0").Replace("'", "")), 0, false);
                                    Program.oMQTT.Publish("Enecsys/Data/" + EDs.SystemID + "/VoltageDC", Encoding.UTF8.GetBytes(EDs.VoltageDC.ToString("N1").Replace("'", "")), 0, false);
                                }
                            }
                            catch { }
                            //Console.WriteLine(sSub);

                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("E155:" + ex.Message);
                        }
                    }

                    mstrResponse = "\n\r";
                }
                if (!string.IsNullOrEmpty(mstrResponse))
                {
                    bytesSent = Encoding.ASCII.GetBytes(mstrResponse);
                    stream.Write(bytesSent, 0, bytesSent.Length);
                }
            }
            catch (Exception ex)
            {
                ex.Message.ToString();
            }
        }
    }
    public class EnecsysData
    {
        const string base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

        public double ConvertFromBin64(string base64input)
        {
            double iResult = 0;



            int i = 0;
            foreach (char a in base64input.ToCharArray())
            {
                iResult = iResult + base64chars.IndexOf(a) * System.Math.Pow(64, base64input.Length - 1 - i);
                i++;
            }

            return iResult;
        }

        public EnecsysData(string EncodedString)
        {
            if (EncodedString.Length >= 47)
            {
                string fixedString = EncodedString.Replace('-', '+');
                fixedString = fixedString.Replace('_', '/');
                byte[] bRes = Convert.FromBase64String(fixedString.Substring(3, 6) + "==");
                SystemID = BitConverter.ToUInt32(bRes, 0);

                double i = ConvertFromBin64(EncodedString.Substring(14, 4));
                TimeSpan iSPan = new TimeSpan(0, 0, (int)i * 30);
                TimeStamp = iSPan;

                ErrorState = EncodedString.Substring(32, 3);
                //DAz = undervoltage

                LastModified = DateTime.Now;
                DCCurrentA = (double)ConvertFromBin64(EncodedString.Substring(35, 2)) / 2000;
                DCPowerW = (double)ConvertFromBin64(EncodedString.Substring(37, 2));
                
                //480W Should be max...
                if (DCPowerW > 500)
                    DCPowerW = 0;

                Efficiency = (double)ConvertFromBin64(EncodedString.Substring(39, 3)) / 40;
                VoltageAC = (double)ConvertFromBin64(EncodedString.Substring(43, 3)) / 4;
                Temperature = (int)ConvertFromBin64(EncodedString.Substring(46, 1));
                CumulativeDCPowerW = (double)ConvertFromBin64(EncodedString.Substring(47, 3)) * 0.25;
                string sACFrequency = EncodedString.Substring(42, 1);
                if (sACFrequency.ToLower() == "x")
                    ACFrequency = 49;
                if (sACFrequency.ToLower() == "y")
                    ACFrequency = 50;

                VoltageDC = DCPowerW / DCCurrentA;
                ACPowerW = DCPowerW * (Efficiency / 100);
                Wh = (double)ConvertFromBin64(EncodedString.Substring(47, 3)) / 4;
                kWh = (double)ConvertFromBin64(EncodedString.Substring(50, 3)) / 16;
                LifekWh = (0.001 * Wh) + kWh;
            }
        }

        public EnecsysData()
        {

            SystemID = 0;
            DCCurrentA = 0;
            DCPowerW = 0;
            ACPowerW = 0;
            Efficiency = 0;
            VoltageAC = 0;
            VoltageDC = 0;
            Temperature = 0;
            CumulativeDCPowerW = 0;
            ACFrequency = 0;
            Wh = 0;
            kWh = 0;
            LifekWh = 0;
            LastModified = DateTime.Now;
        }

        public UInt32 SystemID { get; set; }
        public TimeSpan TimeStamp { get; set; }
        public DateTime LastModified { get; set; }
        public string ErrorState { get; set; }
        /// <summary>
        /// Current in Ampere
        /// </summary>
        public double DCCurrentA { get; set; }
        public double DCPowerW { get; set; }
        public double ACPowerW { get; set; }
        /// <summary>
        /// Efficiency in %
        /// </summary>
        public double Efficiency { get; set; }
        public double VoltageAC { get; set; }
        public double VoltageDC { get; set; }
        public int Temperature { get; set; }
        /// <summary>
        /// cummulative PowerDC in Wh reset after 1kWh
        /// </summary>
        public double CumulativeDCPowerW { get; set; }
        public int ACFrequency { get; set; }

        double Wh { get; set; }
        double kWh { get; set; }
        public double LifekWh { get; set; }


    }
}
